reactrouter.js ➔ instrumentReactRouter   D
last analyzed

Complexity

Conditions 12

Size

Total Lines 68
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 68
c 0
b 0
f 0
rs 4.8
cc 12

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like reactrouter.js ➔ instrumentReactRouter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import { browserTracingIntegration, startBrowserTracingPageLoadSpan, startBrowserTracingNavigationSpan, WINDOW } from '@sentry/browser';
2
import { getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, getActiveSpan, getRootSpan, spanToJSON } from '@sentry/core';
3
import * as React from 'react';
4
import { hoistNonReactStatics } from './hoist-non-react-statics.js';
5
6
// We need to disable eslint no-explicit-any because any is required for the
7
// react-router typings.
8
9
/**
10
 * A browser tracing integration that uses React Router v4 to instrument navigations.
11
 * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
12
 */
13
function reactRouterV4BrowserTracingIntegration(
14
  options,
15
) {
16
  const integration = browserTracingIntegration({
17
    ...options,
18
    instrumentPageLoad: false,
19
    instrumentNavigation: false,
20
  });
21
22
  const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options;
23
24
  return {
25
    ...integration,
26
    afterAllSetup(client) {
27
      integration.afterAllSetup(client);
28
29
      instrumentReactRouter(
30
        client,
31
        instrumentPageLoad,
32
        instrumentNavigation,
33
        history,
34
        'reactrouter_v4',
35
        routes,
36
        matchPath,
37
      );
38
    },
39
  };
40
}
41
42
/**
43
 * A browser tracing integration that uses React Router v5 to instrument navigations.
44
 * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
45
 */
46
function reactRouterV5BrowserTracingIntegration(
47
  options,
48
) {
49
  const integration = browserTracingIntegration({
50
    ...options,
51
    instrumentPageLoad: false,
52
    instrumentNavigation: false,
53
  });
54
55
  const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options;
56
57
  return {
58
    ...integration,
59
    afterAllSetup(client) {
60
      integration.afterAllSetup(client);
61
62
      instrumentReactRouter(
63
        client,
64
        instrumentPageLoad,
65
        instrumentNavigation,
66
        history,
67
        'reactrouter_v5',
68
        routes,
69
        matchPath,
70
      );
71
    },
72
  };
73
}
74
75
function instrumentReactRouter(
76
  client,
77
  instrumentPageLoad,
78
  instrumentNavigation,
79
  history,
80
  instrumentationName,
81
  allRoutes = [],
82
  matchPath,
83
) {
84
  function getInitPathName() {
85
    if (history.location) {
86
      return history.location.pathname;
87
    }
88
89
    if (WINDOW.location) {
90
      return WINDOW.location.pathname;
91
    }
92
93
    return undefined;
94
  }
95
96
  /**
97
   * Normalizes a transaction name. Returns the new name as well as the
98
   * source of the transaction.
99
   *
100
   * @param pathname The initial pathname we normalize
101
   */
102
  function normalizeTransactionName(pathname) {
103
    if (allRoutes.length === 0 || !matchPath) {
104
      return [pathname, 'url'];
105
    }
106
107
    const branches = matchRoutes(allRoutes, pathname, matchPath);
108
    for (const branch of branches) {
109
      if (branch.match.isExact) {
110
        return [branch.match.path, 'route'];
111
      }
112
    }
113
114
    return [pathname, 'url'];
115
  }
116
117
  if (instrumentPageLoad) {
118
    const initPathName = getInitPathName();
119
    if (initPathName) {
120
      const [name, source] = normalizeTransactionName(initPathName);
121
      startBrowserTracingPageLoadSpan(client, {
122
        name,
123
        attributes: {
124
          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
125
          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.${instrumentationName}`,
126
          [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
127
        },
128
      });
129
    }
130
  }
131
132
  if (instrumentNavigation && history.listen) {
133
    history.listen((location, action) => {
134
      if (action && (action === 'PUSH' || action === 'POP')) {
135
        const [name, source] = normalizeTransactionName(location.pathname);
136
        startBrowserTracingNavigationSpan(client, {
137
          name,
138
          attributes: {
139
            [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
140
            [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.${instrumentationName}`,
141
            [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
142
          },
143
        });
144
      }
145
    });
146
  }
147
}
148
149
/**
150
 * Matches a set of routes to a pathname
151
 * Based on implementation from
152
 */
153
function matchRoutes(
154
  routes,
155
  pathname,
156
  matchPath,
157
  branch = [],
158
) {
159
  routes.some(route => {
160
    const match = route.path
161
      ? matchPath(pathname, route)
162
      : branch.length
163
        ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
164
          branch[branch.length - 1].match // use parent match
165
        : computeRootMatch(pathname); // use default "root" match
166
167
    if (match) {
168
      branch.push({ route, match });
169
170
      if (route.routes) {
171
        matchRoutes(route.routes, pathname, matchPath, branch);
172
      }
173
    }
174
175
    return !!match;
176
  });
177
178
  return branch;
179
}
180
181
function computeRootMatch(pathname) {
182
  return { path: '/', url: '/', params: {}, isExact: pathname === '/' };
183
}
184
185
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
186
function withSentryRouting(Route) {
187
  const componentDisplayName = Route.displayName || Route.name;
188
189
  const WrappedRoute = (props) => {
190
    if (props?.computedMatch?.isExact) {
191
      const route = props.computedMatch.path;
192
      const activeRootSpan = getActiveRootSpan();
193
194
      getCurrentScope().setTransactionName(route);
195
196
      if (activeRootSpan) {
197
        activeRootSpan.updateName(route);
198
        activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
199
      }
200
    }
201
202
    // @ts-expect-error Setting more specific React Component typing for `R` generic above
203
    // will break advanced type inference done by react router params:
204
    // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164
205
    return React.createElement(Route, { ...props,} );
206
  };
207
208
  WrappedRoute.displayName = `sentryRoute(${componentDisplayName})`;
209
  hoistNonReactStatics(WrappedRoute, Route);
210
  // @ts-expect-error Setting more specific React Component typing for `R` generic above
211
  // will break advanced type inference done by react router params:
212
  // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164
213
  return WrappedRoute;
214
}
215
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
216
217
function getActiveRootSpan() {
218
  const span = getActiveSpan();
219
  const rootSpan = span && getRootSpan(span);
220
221
  if (!rootSpan) {
222
    return undefined;
223
  }
224
225
  const op = spanToJSON(rootSpan).op;
226
227
  // Only use this root span if it is a pageload or navigation span
228
  return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;
229
}
230
231
export { reactRouterV4BrowserTracingIntegration, reactRouterV5BrowserTracingIntegration, withSentryRouting };
232
//# sourceMappingURL=reactrouter.js.map
233